﻿namespace WebPacker.Packs.Watchers
{
	using System;
	using System.Collections.Generic;
	using System.IO;
	using System.Linq;
	using WebPacker.Helpers;
	using WebPacker.Logger;
	using WebPacker.Packs.Resources;

	public class FileWatcher : BaseResourceWatcher
	{
		private readonly IList<FileSystemWatcher> watchers;
		private readonly FileSystemEventHandler changedHandler;
		private readonly ResourceChangedEventQueue eventQueue;

		public FileWatcher()
		{
			this.watchers = new List<FileSystemWatcher>();
			this.changedHandler = new FileSystemEventHandler(ResourceChanged);
			this.eventQueue = new ResourceChangedEventQueue();
			this.eventQueue.Changed += OnChanged;
		}

		public override event EventHandler<ResourceChangedEventArgs> Changed;

		public int NotifyDelay
		{
			get { return eventQueue.NotifyDelay; }
			set { eventQueue.NotifyDelay = value; }
		}

		public override bool Accept(IResource resource)
		{
			return resource is FileResource;
		}

		public override void Clear()
		{
			lock (syncRoot)
			{
				resources.Clear();

				foreach (var watcher in watchers)
				{
					watcher.Dispose();
				}

				watchers.Clear();
			}
		}

		protected override void Dispose(bool disposing)
		{
			if (!IsDisposed)
			{
				eventQueue.Dispose();
			}

			base.Dispose(disposing);
		}

		protected override void Watch(IResource resource)
		{
			var path = Path.GetDirectoryName(resource.Name);

			if (!watchers.Any(x => x.Path == path))
			{
				FileSystemWatcher watcher = null;

				try
				{
					watcher = new FileSystemWatcher
					{
						Path = path,
						NotifyFilter = NotifyFilters.LastWrite,
						Filter = "*.*",
						IncludeSubdirectories = false
					};

					watcher.Changed += changedHandler;
					watcher.EnableRaisingEvents = true;

					watchers.Add(watcher);
				}
				catch (Exception)
				{
					if (watcher != null)
					{
						watcher.Dispose();
					}

					throw;
				}
			}
		}

		protected override void Unwatch(IResource resource)
		{
			var path = Path.GetDirectoryName(resource.Name);

			if (!resources.Any(x => Path.GetDirectoryName(x.Name) == path))
			{
				var watcher = watchers.Where(x => x.Path == path).FirstOrDefault();

				if (watcher != null)
				{
					watchers.Remove(watcher);
					watcher.Dispose();
				}
			}
		}

		private void ResourceChanged(object sender, FileSystemEventArgs e)
		{
			if (IsDisposed)
			{
				return;
			}

			string name = null;

			try
			{
				if (Changed != null)
				{
					name = e.FullPath;
					IResource[] changedResources;

					LogFactory.GetLogger().Info(() => string.Format("Resource \"{0}\" is changed.", name));

					lock (syncRoot)
					{
						changedResources = resources.Where(x => VirtualPathHelper.Equals(x.Name, name)).ToArray();
					}

					if (changedResources.Length > 0)
					{
						eventQueue.AddEvent(new ResourceChangedEventArgs(name, changedResources));
					}
				}
			}
			catch (Exception ex)
			{
				LogFactory.GetLogger().Exception(string.Format("Error at process change of resource \"{0}\".", name), ex);
			}
		}

		private void OnChanged(object sender, ResourceChangedEventArgs e)
		{
			if (Changed != null)
			{
				Changed(this, e);
			}
		}
	}
}